[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto skywatched.app
0

Configure Feed

Select the types of activity you want to include in your feed.

skywatched / src / routes / [kind] / [id] / +page.svelte
6.6 kB 194 lines
1<script lang="ts"> 2 import { type PageData } from './$types'; 3 import { cn, nameToId } from '$lib/utils'; 4 import { rateMovieModal, videoPlayer, watchedItems } from '$lib/state.svelte'; 5 6 import Container from '$lib/Components/Container.svelte'; 7 import ItemsList from '$lib/Components/ItemsList.svelte'; 8 import ReviewList from '$lib/Components/ReviewList.svelte'; 9 import Avatar from '$lib/Components/Avatar.svelte'; 10 import { page } from '$app/stores'; 11 12 let { data }: { data: PageData } = $props(); 13</script> 14 15<svelte:head> 16 <title>{data.result.title ?? data.result.name ?? ''} | skywatched</title> 17 18 <meta 19 name="description" 20 content={`Rate and review "${data.result.title ?? data.result.name ?? ''}" on skywatched`} 21 /> 22 23 <meta property="og:url" content={$page.url.href} /> 24 <meta property="og:type" content="website" /> 25 <meta property="og:title" content="{data.result.title ?? data.result.name ?? ''} | skywatched.app" /> 26 <meta 27 property="og:description" 28 content={`Rate and review "${data.result.title ?? data.result.name ?? ''}" on skywatched`} 29 /> 30 <meta property="og:image" content="{$page.url.href}/og.png" /> 31 32 <meta name="twitter:card" content="summary_large_image" /> 33 <meta property="twitter:domain" content="skywatched.app" /> 34 <meta property="twitter:url" content={$page.url.href} /> 35 <meta name="twitter:title" content="{data.result.title ?? data.result.name ?? ''} | skywatched.app" /> 36 <meta 37 name="twitter:description" 38 content={`Rate and review "${data.result.title ?? data.result.name ?? ''}" on skywatched`} 39 /> 40 <meta name="twitter:image" content="{$page.url.href}/og.png" /> 41</svelte:head> 42 43{#snippet buttons()} 44 {#if data.user && !watchedItems.hasRated(data.result)} 45 <button 46 class={cn( 47 'inline-flex items-center gap-2 rounded-md bg-white/10 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-75 hover:bg-white/20 ', 48 watchedItems.hasRated(data.result) 49 ? 'bg-green-500/10 text-green-400 hover:bg-green-500/20' 50 : '' 51 )} 52 onclick={() => { 53 rateMovieModal.show({ 54 movieId: data.result.movieId, 55 showId: data.result.showId, 56 kind: data.result.movieId ? 'movie' : 'tv', 57 name: data.result.original_title ?? data.result.original_name, 58 posterPath: data.result.poster_path, 59 currentRating: watchedItems.getRating(data.result)?.rating ?? 0, 60 currentReview: watchedItems.getRating(data.result)?.ratingText ?? '' 61 }); 62 }} 63 > 64 rate {data.kind === 'movie' ? 'movie' : 'show'} 65 </button> 66 {/if} 67 {#if data.trailer} 68 <button 69 onclick={() => videoPlayer.show(data.trailer ?? '')} 70 type="button" 71 class="inline-flex w-fit items-center gap-x-1.5 rounded-md border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600" 72 > 73 <svg 74 xmlns="http://www.w3.org/2000/svg" 75 viewBox="0 0 24 24" 76 fill="currentColor" 77 class="-ml-0.5 size-5" 78 > 79 <path 80 d="M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z" 81 /> 82 </svg> 83 84 Trailer 85 </button> 86 {/if} 87{/snippet} 88 89<img 90 src="https://image.tmdb.org/t/p/w780{data.result.backdrop_path}" 91 alt="" 92 class="fixed h-full w-full object-cover object-center opacity-20" 93/> 94<div class="fixed inset-0 h-full w-full bg-black/50"></div> 95 96<Container class="relative z-10 pb-8 pt-4"> 97 <div class="flex gap-4 px-4 pt-8"> 98 <img 99 src="https://image.tmdb.org/t/p/w500{data.result.poster_path}" 100 alt="" 101 class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 102 style:--name={`poster-${data.result.id}`} 103 /> 104 <div class="flex flex-col gap-4"> 105 <div 106 class="title max-w-xl text-2xl font-semibold text-white sm:text-4xl" 107 style:--name={`title-${data.result.id}`} 108 > 109 {data.result.title ?? data.result.name} 110 </div> 111 112 {#if data.settings?.streaming_region?.code && data.watchProviders[data.settings.streaming_region.code]?.flatrate} 113 <div class="mt-2 text-sm text-white sm:mt-4"> 114 <div class="mb-2 flex flex-wrap gap-4 text-xs font-medium"> 115 stream on 116 <span class="flex items-center gap-2 text-base-400" 117 >from <a 118 href="https://www.justwatch.com" 119 target="_blank" 120 class="text-base-300 hover:text-accent-300" 121 > 122 <img src="/justwatch_logo.svg" alt="justwatch" class="h-3" /> 123 </a></span 124 > 125 </div> 126 <a 127 href={data.watchProviders[data.settings.streaming_region.code].link} 128 target="_blank" 129 class="flex flex-wrap gap-2" 130 > 131 {#each data.watchProviders[data.settings.streaming_region.code].flatrate as provider} 132 <img 133 src="https://image.tmdb.org/t/p/w500{provider.logo_path}" 134 alt={provider.provider_name} 135 class="size-8 rounded-md border border-base-800 md:size-12" 136 /> 137 {/each} 138 </a> 139 </div> 140 {/if} 141 <div class="hidden gap-2 sm:flex"> 142 {@render buttons()} 143 </div> 144 </div> 145 </div> 146 147 <div class="px-4 pt-4 text-sm text-white"> 148 <div class="mb-4 flex gap-2 sm:hidden"> 149 {@render buttons()} 150 </div> 151 <div class="mb-4 max-w-2xl text-sm text-white"> 152 <div class="mb-2 text-lg font-semibold">overview</div> 153 {data.result.overview} 154 </div> 155 </div> 156 157 {#if data.ratings.length > 0} 158 <div class="mt-8 px-4 text-lg font-semibold">reviews</div> 159 160 <ReviewList reviews={data.ratings} showMovieDetails={false} class="" /> 161 {/if} 162 163 {#if data.recommendations.length > 0} 164 <div class="px-4 pt-4 text-sm text-white"> 165 <div class="mb-2 text-lg font-semibold">recommendations</div> 166 167 <ItemsList items={data.recommendations} showMark={!!data.user} /> 168 </div> 169 {/if} 170 171 {#if data.cast.length > 0} 172 <div class="px-4 pb-8 pt-4 text-sm text-white"> 173 <div class="mb-2 text-lg font-semibold">cast</div> 174 175 <div class={cn('flex gap-x-6 overflow-x-auto')}> 176 {#each data.cast as castMember} 177 <a 178 href={`/cast/${castMember.id}-${nameToId(castMember.name)}`} 179 class="flex flex-col items-center gap-1" 180 > 181 <Avatar 182 src={castMember.profile_path 183 ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path 184 : undefined} 185 size="size-32" 186 /> 187 <div class="text-center text-xs font-medium">{castMember.name}</div> 188 <div class="text-center text-xs text-base-400">{castMember.character}</div> 189 </a> 190 {/each} 191 </div> 192 </div> 193 {/if} 194</Container>